<?
/**
 * Copyright 2007 Melange.
 *
 * This file is part of PHP-HTTP.
 *
 * PHP-HTTP is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * PHP-HTTP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with PHP-HTTP; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * @category    Melange
 * @package     php-http
 * @copyright   Copyright (c) 2007 Jeroen Simons. All rights reserved
 * @author      Jeroen Simons <jeroen@melange.nl>
 * @link        http://www.melange.nl/
 *
 */

require_once("http/Locale.php");

/**
 * Wrapper around a <code>$_POST, $_GET and $_FILES</code>
 */
class Request {


    // -------------------------------------------------------------- Constants


    /**
     * scheme constants
     */
    const SCHEME_HTTP  = "http";
    const SCHEME_HTTPS = "https";


    /**
     * The name of the cookie used to pass the session identifier back
     * and forth with the client.
     */
    const SESSION_COOKIE_NAME = "PHPSESSID";


    // ----------------------------------------------------- Instance Variables


    /**
     * The attributes associated with this Request, keyed by attribute name.
     */
    protected $attributes = array();


    /**
     * The authentication type used for this request.
     */
    protected $authType = null;


    /**
     * Cookies parsed flag.
     */
    protected $cookiesParsed = false;


    /**
     * The set of cookies associated with this Request.
     */
    protected $cookies = null;


    /**
     * The Context within which this Request is being processed.
     */
    protected $context = null;


    /**
     * The content length associated with this request.
     */
    protected $contentLength = -1;


    /**
     * The context path for this request.
     */
    protected $contextPath = "";


    /**
     * The decoded request URI associated with this request.
     */
    protected $decodedRequestURI = null;


    /**
     * The HTTP headers associated with this Request, keyed by name.  The
     * values are ArrayLists of the corresponding header values.
     */
    protected $headers = array();


    /**
     * The default Locale if none are specified.
     */
    protected $defaultLocale;


    /**
     * The preferred Locales assocaited with this Request.
     */
    protected $locales = array();


    /**
     * Parse locales.
     */
    protected $localesParsed = false;


    /**
     * The request method associated with this Request.
     */
    protected $method = null;


    /**
     * Internal notes associated with this request by Catalina components
     * and event listeners.
     */
    private $notes = array();


    /**
     * The parsed parameters for this request.  This is populated only if
     * parameter information is requested via one of the
     * <code>getParameter()</code> family of method calls.  The key is the
     * parameter name, while the value is a String array of values for this
     * parameter.
     * <p>
     * <strong>IMPLEMENTATION NOTE</strong> - Once the parameters for a
     * particular request are parsed and stored here, they are not modified.
     * Therefore, application level access to the parameters need not be
     * synchronized.
     */
    protected $parameters = null;


    /**
     * Have the parameters for this request been parsed yet?
     */
    protected $parsed = false;


    /**
     * The path information for this request.
     */
    protected $pathInfo = null;


    /**
     * The protocol name and version associated with this Request.
     */
    protected $protocol = null;


    /**
     * The query string for this request.
     */
    protected $queryString = null;


    /**
     * Was the requested session ID received in a cookie?
     */
    protected $requestedSessionCookie = false;


    /**
     * The requested session ID (if any) for this request.
     */
    protected $requestedSessionId = null;


    /**
     * Was the requested session ID received in a URL?
     */
    protected $requestedSessionURL = false;


    /**
     * The request URI associated with this request.
     */
    protected $requestURI = null;


    /**
     * The scheme associated with this Request.
     */
    protected $scheme = null;


    /**
     * Was this request received on a secure channel?
     */
    protected $secure = false;


    /**
     * The server name associated with this Request.
     */
    protected $serverName = null;


    /**
     * The server port associated with this Request.
     */
    protected $serverPort = -1;


    /**
     * The servlet path for this request.
     */
    protected $servletPath = null;


    /**
     * The currently active session for this request.
     */
    protected $session = null;


    /**
     * The Principal who has been authenticated for this Request.
     */
    protected $userPrincipal = null;


    // ----------------------------------------------------------- Constructors


    /**
     * Construct a new Request using a requestURI
     */
    public function __construct($uri) {

        $this->defaultLocale = Locale::getDefault();

        // Set the ServerPort
        $this->serverPort = intval($_SERVER["SERVER_PORT"]);


        // Is there a session cookie set ?
        if(!empty($_COOKIE[self::SESSION_COOKIE_NAME])) {
            $this->setRequestedSessionCookie(true);
            $this->setRequestedSessionId(session_id());
        }

        // Set the queryString
        if (false !== ($question = strpos($uri, '?'))) {

            $this->queryString = substr($uri, $question + 1);

            // Came the sessionId with the URI ?
            if($pos = strpos($this->queryString, self::SESSION_COOKIE_NAME."=") !== false) {
                $substr = substr($this->queryString, $pos + strlen(self::SESSION_COOKIE_NAME));
                if($pos = strpos($substr, "&") !== false) {
                    $substr = substr($substr, 0, $pos);
                }
                $this->setRequestedSessionId($substr);
                $this->setRequestedSessionURL(true);
            }


            $uri = substr($uri, 0, $question);
        }

        // Checking for an absolute URI (with the HTTP protocol)
        if (strpos($uri, "/") !== 0) {
            $pos = strpos($uri, "://");;
            // Parsing out protocol and host name
            if ($pos !== false) {

                // Set the Scheme
                $this->scheme = substr($uri, 0, $pos);

                $pos = strpos($uri, "/", $pos + 3);
                if ($pos === false) {
                    $uri = "";
                } else {
                    $uri = substr($uri, $pos);
                }
            }
        } else {

            // Guess the Scheme
            switch(true) {

                case (!empty($_SERVER["HTTPS"])):
                    $this->scheme = self::SCHEME_HTTPS;
                    break;

                 // Apache1 does not export $_SERVER['HTTPS']
                case ($this->serverPort == 443):
                    $this->scheme = self::SCHEME_HTTPS;
                    break;

                default:
                    $this->scheme = self::SCHEME_HTTP;

            }

        }


        // Set the RequestURI
        $this->requestURI = $uri;


        // Set the Method
        $this->method = $_SERVER["REQUEST_METHOD"];


        // Set the Protocol
        $this->protocol = $_SERVER["SERVER_PROTOCOL"];


        // Set the ServerName
        $this->serverName = defined('SERVER_NAME') ? SERVER_NAME : $_SERVER["SERVER_NAME"];


        // Set the headers
        switch(true) {

            case !empty($_SERVER["CONTENT_TYPE"]):

                $contentType = $_SERVER["CONTENT_TYPE"];
                if (false !== ($semicolon = strpos($contentType, ";"))) {

                    if (false !== ($charset = stripos($contentType, "charset"))) {
                        $charset = trim(substr($contentType, $charSet + 1));
                        if (false !== ($equals = strpos($charset, "="))) {
                            $this->characterEncoding = trim(substr($charset, $equals + 1));
                        }
                    }

                    $this->contentType = trim(substr($contentType, 0, $semicolon));
                } else {
                    $this->contentType = $contentType;
                }

                $this->addHeader("Content-Type", $this->contentType);

            case !empty($_SERVER["CONTENT_LENGTH"]):
                $this->contentLength = intval($_SERVER["CONTENT_LENGTH"]);
                $this->addHeader("Content-Length", $this->contentLength);


        }

    }


    // --------------------------------------------------------- Public Methods


    /**
     * Return the value of the specified request attribute, if any; otherwise,
     * return <code>null</code>.  If there is more than one value defined,
     * return only the first one.
     *
     * @param name Name of the desired request attribute
     */
    public function getAttribute($name) {

        return array_key_exists($name, $this->attributes) ?
            $this->attributes[$name] : null;

    }


    /**
     * Return the names of all defined request attributes for this request.
     */
    public function getAttributeNames() {

        return (array_keys($this->attributes));

    }


    /**
     * Remove the specified request attribute if it exists.
     *
     * @param name Name of the request attribute to remove
     */
    public function removeAttribute($key) {
        if (isset($this->attributes[$key])) {
            unset($this->attributes[$key]);
        }
    }


    /**
     * Set the specified request attribute to the specified value.
     *
     * @param name Name of the request attribute to set
     * @param value The associated value
     */
    public function setAttribute($name, $value) {

        // Name cannot be null
        if ($name === null)
            throw new Exception
                ("Name cannot be null");

        // Null value is the same as removeAttribute()
        if ($value == null) {
            $this->removeAttribute($name);
            return;
        }

       $this->attributes[$name] = $value;

    }


    /**
     * Return the character encoding for this Request.
     */
    public function getCharacterEncoding() {

      return ($this->characterEncoding);

    }


    /**
     * Return the set of Cookies received with this Request.
     */
    public function getCookies() {

        if (!$this->cookiesParsed)
            $this->parseCookies();

        return $this->cookies;

    }


    /**
     * Return the content length for this Request.
     */
    public function getContentLength() {

        return ($this->contentLength);

    }


    /**
     * Return the content type for this Request.
     */
    public function getContentType() {

        return ($this->contentType);

    }


    /**
     * Return the Context within which this Request is being processed.
     */
    public function getContext() {
        return ($this->context);
    }


    /**
     *
     * Returns the portion of the request URI that indicates the context
     * of the request.  The context path always comes first in a request
     * URI.  The path starts with a "/" character but does not end with a "/"
     * character.  For servlets in the default (root) context, this method
     * returns "". The container does not decode this string.
     *
     *
     * @return		a <code>String</code> specifying the
     *			    portion of the request URI that indicates the context
     *			    of the request
     */
    public function getContextPath() {
        return $this->contextPath;
    }


    /**
     * Set the context path for this Request.  This will normally be called
     * when the associated Context is mapping the Request to a particular
     * Wrapper.
     *
     * @param path The context path
     */
    public function setContextPath($path) {

        if ($path === null) {
            $this->contextPath = "";
        } else {
            $this->contextPath = $path;
        }

    }


    /**
     *
     * Set the Context within which this Request is being processed.  This
     * must be called as soon as the appropriate Context is identified, because
     * it identifies the value to be returned by <code>getContextPath()</code>,
     * and thus enables parsing of the request URI.
     *
     * @param context The newly associated Context
     */
    public function setContext($context) {
        $this->context = $context;
        $this->contextPath = $context->getPath();
    }


    /**
     *
     * Returns the name of the HTTP method with which this
     * request was made, for example, GET, POST, or PUT.
     * Same as the value of the CGI variable REQUEST_METHOD.
     *
     * @return			a <code>String</code>
     *				specifying the name
     *				of the method with which
     *				this request was made
     */
    public function getMethod() {

        return $this->method;

    }


    /**
     * Return the query string associated with this request.
     */
    public function getQueryString() {

        return ($this->queryString);

    }


    /**
     * Sets the query string associated with this request.
     */
    public function setQueryString($queryString) {

        $this->queryString = $queryString;

    }


    /**
     * Set a flag indicating whether or not the requested session ID for this
     * request came in through a cookie.  This is normally called by the
     * HTTP Connector, when it parses the request headers.
     *
     * @param flag The new flag
     */
    public function setRequestedSessionCookie($flag) {

        $this->requestedSessionCookie = $flag;

    }


    /**
     * Set the requested session ID for this request.  This is normally called
     * by the HTTP Connector, when it parses the request headers.
     *
     * @param id The new session id
     */
    public function setRequestedSessionId($id) {

        $this->requestedSessionId = $id;

    }


    /**
     * Set a flag indicating whether or not the requested session ID for this
     * request came in through a URL.  This is normally called by the
     * HTTP Connector, when it parses the request headers.
     *
     * @param flag The new flag
     */
    public function setRequestedSessionURL($flag) {

        $this->requestedSessionURL = $flag;

    }


    /**
     * Return the request URI for this request.
     */
    public function getRequestURI() {

        return ($this->requestURI);

    }


    /**
     * Set the unparsed request URI for this Request.
     *
     * @param uri The request URI
     */
    public function setRequestURI($requestURI) {

        $this->requestURI = $requestURI;

    }


    /**
     * Return the base URL for this request.
     */
    public function getBaseURL() {

        return $this->getScheme()."://" . $this->getServerName() .
            $this->getContextPath() . "/";

    }


    /**
     * Set the decoded request URI.
     *
     * @param uri The decoded request URI
     */
    public function setDecodedRequestURI($uri) {

        $this->decodedRequestURI = $uri;

    }


    /**
     * Return the URL decoded request URI.
     */
    public function getDecodedRequestURI() {

        if ($this->decodedRequestURI == null)
            $this->decodedRequestURI = urldecode($this->getRequestURI());

        return $this->decodedRequestURI;

    }


    /**
     *
     * Returns any extra path information associated with
     * the URL the client sent when it made this request.
     * The extra path information follows the servlet path
     * but precedes the query string.
     * This method returns <code>null</code> if there
     * was no extra path information.
     *
     * <p>Same as the value of the CGI variable PATH_INFO.
     *
     *
     * @return		a <code>String</code>, decoded by the
     *			web container, specifying
     *			extra path information that comes
     *			after the servlet path but before
     *			the query string in the request URL;
     *			or <code>null</code> if the URL does not have
     *			any extra path information
     *
     */

    public function getPathInfo() {
        if($this->pathInfo === null)
            $this->pathInfo = $this->computePathInfo();
        return $this->pathInfo;
    }


    /**
     * Set the path information for this Request.  This will normally be called
     * when the associated Context is mapping the Request to a particular
     * Wrapper.
     *
     * @param path The path information
     */
    public function setPathInfo($path) {
        $this->pathInfo = $path;
    }


    /**
     * Add a parameter name and corresponding set of values to this Request.
     * (This is used when restoring the original request on a form based
     * login).
     *
     * @param name Name of this request parameter
     * @param values Corresponding values for this request parameter
     */
    public function addParameter($name, $values) {

        $this->parameters[$name] = $values;

    }


    /**
     * Return the value of the specified request parameter, if any; otherwise,
     * return <code>null</code>.  If there is more than one value defined,
     * return only the first one.
     *
     * @param name Name of the desired request parameter
     */
    public function getParameter($name) {

        $this->parseParameters();

        $values = array_key_exists($name, $this->parameters) ?
            $this->parameters[$name] : null;

        if ($values !== null)
            return is_array($values) ? ($values[0]) : $values;
        else
            return (null);

    }


    /**
     * Returns a <code>Map</code> of the parameters of this request.
     * Request parameters are extra information sent with the request.
     * For HTTP servlets, parameters are contained in the query string
     * or posted form data.
     *
     * @return A <code>Map</code> containing parameter names as keys
     *  and parameter values as map values.
     */
    public function getParameterMap() {

        $this->parseParameters();
        return ($this->parameters);

    }


    /**
     * Return the names of all defined request parameters for this request.
     */
    public function getParameterNames() {

        $this->parseParameters();
        return (array_keys($this->parameters));

    }


    /**
     * Return the defined values for the specified request parameter, if any;
     * otherwise, return <code>null</code>.
     *
     * @param name Name of the desired request parameter
     */
    public function getParameterValues($name) {

        $this->parseParameters();

        $values = array_key_exists($name, $this->parameters) ?
            $this->parameters[$name] : null;

        if ($values !== null)
            return ($values);
        else
            return (null);

    }


    /**
     * Parse cookies.
     */
    protected function parseCookies() {

        require_once("http/Cookie.php");

        $this->cookiesParsed = true;
        $this->cookies = array();

        $serverCookies = $_COOKIE;
        unset($serverCookies[self::SESSION_COOKIE_NAME]);
        $count = count($serverCookies);
        if ($count <= 0)
            return;

        while(list($name, $value) = each($serverCookies)) {
            $cookie = new Cookie($name, $value);
            $this->cookies[] = $cookie;
        }

    }


    /**
     * Return the first value of the specified header, if any; otherwise,
     * return <code>null</code>
     *
     * @param name Name of the requested header
     */
    public function getHeader($name) {

        $name = strtolower($name);
        $values = array_key_exists($name, $this->headers) ?
            $this->headers[$name] : null;
        if ($values !== null)
            return $values[0];
        else
                return (null);

    }


    /**
     * Return all of the values of the specified header, if any; otherwise,
     * return an empty enumeration.
     *
     * @param name Name of the requested header
     */
    public function getHeaders($name) {

        $name = strtolower($name);
        $values = array_key_exists($name, $this->headers) ? $this->headers[$name] : null;
        if ($values !== null)
            return array_values($values);
        else
            return (array());

    }


    /**
     * Return the value of the specified date header, if any; otherwise
     * return -1.
     *
     * @param name Name of the requested date header
     *
     * @exception IllegalArgumentException if the specified header value
     *  cannot be converted to a date
     */
    public function getDateHeader($name) {

        $value = $this->getHeader($name);

        if ($value === null)
            return (-1);

        return strtotime($value);

    }


    /**
     * Add a Header to the set of Headers associated with this Request.
     *
     * @param name The new header name
     * @param value The new header value
     */
    public function addHeader($name, $value) {

        $name = strtolower($name);

        $values = array_key_exists($name, $this->headers) ? $this->headers[$name] : null;
        if ($values === null) {
            $this->headers[$name] = array();
        }
        $this->headers[$name][] = $value;

    }


    /**
     *
     * Returns an enumeration of all the header names
     * this request contains. If the request has no
     * headers, this method returns an empty enumeration.
     *
     * <p>Some servlet containers do not allow do not allow
     * servlets to access headers using this method, in
     * which case this method returns <code>null</code>
     *
     * @return			an enumeration of all the
     *				header names sent with this
     *				request; if the request has
     *				no headers, an empty enumeration;
     *				if the servlet container does not
     *				allow servlets to use this method,
     *				<code>null</code>
     *
     */
    public function getHeaderNames() {
        return array_keys($this->headers);
    }


    /**
     * Return <code>true</code> if the authenticated user principal
     * possesses the specified role name.
     *
     * @param role Role name to be validated
     */
    public function isUserInRole($role) {

        // Have we got an authenticated principal at all?
        if ($this->userPrincipal === null)
            return (false);

        // Identify the Realm we will use for checking role assignmenets
        if ($this->context === null)
            return (false);

        $realm = $this->context->getRealm();
        if ($realm === null)
            return (false);

        // Check for a role defined directly as a <security-role>
        return ($realm->hasRole($this->userPrincipal, $role));

    }


    /**
     * Return the preferred Locale that the client will accept content in,
     * based on the value for the first <code>Accept-Language</code> header
     * that was encountered.  If the request did not specify a preferred
     * language, the server's default Locale is returned.
     */
//    public function getLocale() {
//        // en-us,en;q=0.8,nl;q=0.5,af;q=0.3; (firefox);
//        // en (safari)
////        $locales = explode(",", $_SERVER["HTTP_ACCEPT_LANGUAGE"]);
////        if (count($locales) > 0) {
////            $locales = explode("-", $locales[0]);
////            return new Locale($locales[0], count($locales) > 1 ? $locales[1] : "");
////        } else {
////            return ($this->defaultLocale);
////        }
//        throw new Exception("Implement me!");
//
//    }


    /**
     * Return the preferred Locale that the client will accept content in,
     * based on the value for the first <code>Accept-Language</code> header
     * that was encountered.  If the request did not specify a preferred
     * language, $the server's default Locale is returned.
     */
    public function getLocale() {

        if (!$this->localesParsed)
            $this->parseLocales();

        if (count($this->locales) > 0) {
            return ($this->locales[0]);
        } else {
            return ($this->defaultLocale);
        }

    }


    /**
     * Return the set of preferred Locales that the client will accept
     * content in, $based on the values for any <code>Accept-Language</code>
     * headers that were encountered.  If the request did not specify a
     * preferred language, $the server's default Locale is returned.
     */
    public function getLocales() {

        if (!$this->localesParsed)
            $this->parseLocales();

        if (count($this->locales) > 0)
            return ($this->locales);
        $results = array();
        $results[] = $defaultLocale;
        return ($results);

    }


    /**
     * Bind an object to a specified name in the internal notes associated
     * with this request, replacing any existing binding for this name.
     *
     * @param name Name to which the object should be bound
     * @param value Object to be bound to the specified name
     */
    public function setNote($name, $value) {
        $this->notes[$name] = $value;
    }

    /**
     * Return the object bound with the specified name to the internal notes
     * for this request, or <code>null</code> if no such binding exists.
     *
     * @param name Name of the note to be returned
     */
    public function getNote($name) {
        return array_key_exists($name, $this->notes) ? $this->notes[$name] : null;
    }


    /**
     * Return the authentication type used for this Request.
     */
    public function getAuthType() {
        return ($this->authType);
    }


    /**
     * Set the authentication type used for this request, if any; otherwise
     * set the type to <code>null</code>.  Typical values are "BASIC",
     * "DIGEST", or "SSL".
     *
     * @param authType The authentication type used
     */
    public function setAuthType($authType) {
        $this->authType = $this->authType;
    }


    /**
     * Set the Principal who has been authenticated for this Request.  This
     * value is also used to calculate the value to be returned by the
     * <code>getRemoteUser()</code> method.
     *
     * @param principal The user Principal
     */
    public function setUserPrincipal(GenericPrincipal $principal) {
        $this->userPrincipal = $principal;
    }


    /**
     * Return the principal that has been authenticated for this Request.
     */
    public function getUserPrincipal() {
        return ($this->userPrincipal);
    }


    /**
     * Return the real path of the specified virtual path.
     *
     * @param path Path to be translated
     * @deprecated As of version 2.1 of the Java Servlet API, use
     *             <code>ServletContext.getRealPath()</code>.
     */
    public function getRealPath($path) {

        if ($this->context === null)
            return (null);
        $servletContext = $context->getServletContext();
        if ($servletContext === null)
            return (null);
        else {
            try {
                return ($servletContext->getRealPath($path));
            } catch (IllegalArgumentException $e) {
                return (null);
            }
        }

    }


    /**
     * Return the name of the remote user that has been authenticated
     * for this Request.
     */
    public function getRemoteUser() {

        if ($this->userPrincipal !== null)
            return ($this->userPrincipal->getName());
        else
            return (null);

    }


    /**
     * Set the value to be returned by <code>isSecure()</code>
     * for this Request.
     *
     * @param secure The new isSecure value
     */
    public function setSecure($secure) {
        $this->secure = $secure;
    }


    /**
     * Was this request received on a secure connection?
     */
    public function isSecure() {
        return ($this->secure);
    }


    /**
     * Return the object bound with the specified name to the internal notes
     * for this request, or <code>null</code> if no such binding exists.
     *
     * @param name Name of the note to be returned
     */
    public function getSession($create = false) {

        if($this->session !== null)
            return $this->session;

        require_once("http/Session.php");

        $this->session = new Session();

        return $this->session;
    }


    /**
     * Return <code>true</code> if the session identifier included in this
     * request came from a cookie.
     */
    public function isRequestedSessionIdFromCookie() {

        if ($this->requestedSessionId !== null)
            return ($this->requestedSessionCookie);
        else
            return (false);

    }


    /**
     * Return <code>true</code> if the session identifier included in this
     * request came from the request URI.
     */
    public function isRequestedSessionIdFromURL() {

        if ($this->requestedSessionId !== null)
            return ($this->requestedSessionURL);
        else
            return (false);

    }


    /**
     * Return the portion of the request URI used to select the servlet
     * that will process this request.
     */
    public function getServletPath() {
        if($this->servletPath == null)
            $this->servletPath = $this->computeServletPath();
        return ($this->servletPath);

    }


    /**
     * Clear the collection of Cookies associated with this Request.
     */
    public function clearCookies() {
        $sessionName   = self::SESSION_COOKIE_NAME;
        $sessionCookie = session_get_cookie_params();
        setcookie($sessionName, false, $sessionCookie['lifetime'], $sessionCookie['path'], $sessionCookie['domain'], $sessionCookie['secure']);
    }


    /**
     * Clear the collection of Headers associated with this Request.
     */
    public function clearHeaders() {
        $this->headers = array();
    }


    /**
     * Clear the collection of Locales associated with this Request.
     */
    public function clearLocales() {
        $this->locales = array();
    }


    /**
     * Clear the collection of parameters associated with this Request.
     */
    public function clearParameters() {
        $this->_params = array();
    }

    /**
     * Return an Iterator containing the String names of all notes bindings
     * that exist for this request.
     */
    public function getNoteNames() {
        return array_keys($this->notes);
    }


    /**
     * Returns the name and version of the protocol the request uses
     * in the form <i>protocol/majorVersion.minorVersion</i>, for
     * example, HTTP/1.1. For HTTP servlets, the value
     * returned is the same as the value of the CGI variable
     * <code>SERVER_PROTOCOL</code>.
     *
     * @return		a <code>String</code> containing the protocol
     *			name and version number
     *
     */
    public function getProtocol() {
        return $this->protocol;
    }




    /**
     * Returns the name of the scheme used to make this request,
     * for example,
     * <code>http</code>, <code>https</code>, or <code>ftp</code>.
     * Different schemes have different rules for constructing URLs,
     * as noted in RFC 1738.
     *
     * @return		a <code>String</code> containing the name
     *			of the scheme used to make this request
     *
     */
    public function getScheme() {
        return $this->scheme;
    }




    /**
     * Returns the host name of the server that received the request.
     * For HTTP servlets, same as the value of the CGI variable
     * <code>SERVER_NAME</code>.
     *
     * @return		a <code>String</code> containing the name
     *			of the server to which the request was sent
     */
    public function getServerName() {
        return $this->serverName;
    }




    /**
     * Returns the port number on which this request was received.
     * For HTTP servlets, same as the value of the CGI variable
     * <code>SERVER_PORT</code>.
     *
     * @return		an integer specifying the port number
     *
     */
    public function getServerPort() {
        return $this->serverPort;
    }


    // ------------------------------------------------------ Protected Methods


    /**
     * Parse the parameters of this request, if it has not already occurred.
     * If parameters are present in both the query string and the request
     * content, they are merged.
     */
    protected function parseParameters() {

        if ($this->parsed)
            return;

        if ($this->parameters === null)
            $this->parameters = array();

        // Parse any parameters specified in the query string
        parse_str($this->getQueryString(), $this->parameters);

        if (("POST" == $this->getMethod()) && ($this->getContentLength() > 0)
            && ("application/x-www-form-urlencoded" == $this->getContentType())) {

            $this->parameters += $_POST;

        } else if (("POST" == $this->getMethod()) && ($this->getContentLength() > 0)
            && ("multipart/form-data" == $this->getContentType())) {

            $this->parameters += $_POST;
            $this->parameters += $_FILES;

        }

        // Store the final results
        $this->parsed = true;

    }


    // -------------------------------------------------------- Private Methods


    /**
     * Set the servletPath of the request; i.e., the segment leading to the script name
     *
     * Code taken from Zend_Controller_Request_Http
     *
     * E.g.:
     * - /admin
     * - /myapp
     * - /subdir/index.php
     *
     * Do not use the full URI when providing the base. The following are
     * examples of what not to use:
     * - http://example.com/admin (should be just /admin)
     * - http://example.com/subdir/index.php (should be just /subdir/index.php)
     *
     * If no $baseUrl is provided, attempts to determine the base URL from the
     * environment, using SCRIPT_FILENAME, SCRIPT_NAME, PHP_SELF, and
     * ORIG_SCRIPT_NAME in its determination.
     *
     * @param mixed $baseUrl
     * @return Zend_Controller_Request_Http
     */
    public function computeServletPath()
    {
        $filename = basename($_SERVER['SCRIPT_FILENAME']);

        if (basename($_SERVER['SCRIPT_NAME']) === $filename) {
            $baseUrl = $_SERVER['SCRIPT_NAME'];
        } elseif (basename($_SERVER['PHP_SELF']) === $filename) {
            $baseUrl = $_SERVER['PHP_SELF'];
        } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $filename) {
            $baseUrl = $_SERVER['ORIG_SCRIPT_NAME']; // 1and1 shared hosting compatibility
        } else {
            // Backtrack up the script_filename to find the portion matching
            // php_self
            $path    = $_SERVER['PHP_SELF'];
            $segs    = explode('/', trim($_SERVER['SCRIPT_FILENAME'], '/'));
            $segs    = array_reverse($segs);
            $index   = 0;
            $last    = count($segs);
            $baseUrl = '';
            do {
                $seg     = $segs[$index];
                $baseUrl = '/' . $seg . $baseUrl;
                ++$index;
            } while (($last > $index) && (false !== ($pos = strpos($path, $baseUrl))) && (0 != $pos));
        }

        // Does the baseUrl have anything in common with the request_uri?
        $requestUri = $this->requestURI;

        if (0 === strpos($requestUri, $baseUrl)) {
            // full $baseUrl matches
            return $baseUrl;
        }

        if (0 === strpos($requestUri, dirname($baseUrl))) {
            // directory portion of $baseUrl matches
            return rtrim(dirname($baseUrl), '/');
        }

        if (!strpos($requestUri, basename($baseUrl))) {
            // no match whatsoever; set it blank
            return "";
        }

        // If using mod_rewrite or ISAPI_Rewrite strip the script filename
        // out of baseUrl. $pos !== 0 makes sure it is not matching a value
        // from PATH_INFO or QUERY_STRING
        if ((strlen($requestUri) >= strlen($baseUrl))
            && ((false !== ($pos = strpos($requestUri, $baseUrl))) && ($pos !== 0)))
        {
            $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
        }

        return rtrim($baseUrl, '/');
    }


    /**
     * Set the PATH_INFO string
     *
     * Code taken from Zend_Controller_Request_Http
     *
     */
    private function computePathInfo()
    {
        $baseUrl = $this->getServletPath();

        if ((null !== $baseUrl)
            && (false === ($pathInfo = substr($this->requestURI, strlen($baseUrl)))))
        {
            // If substr() returns false then PATH_INFO is set to an empty string
            $pathInfo = "";
        } elseif (null === $baseUrl) {
            $pathInfo = $this->requestURI;
        }

        return (string) $pathInfo;
    }


    /**
     * Add a Locale to the set of preferred Locales for this Request.  The
     * first added Locale will be the first one returned by getLocales().
     *
     * @param locale The new preferred Locale
     */
    public function addLocale(Locale $locale) {
        $this->locales[] = $locale;
    }


    /**
     * Parse request locales.
     */
    protected function parseLocales() {

        $this->localesParsed = true;
        $this->parseLocalesHeader($_SERVER["HTTP_ACCEPT_LANGUAGE"]);

    }


    /**
     * Parse accept-language header value.
     */
    protected function parseLocalesHeader($value) {

        // Store the accumulated languages that have been requested in
        // a local collection, $sorted by the quality value ($so we can
        // add Locales in descending order).  The values will be ArrayLists
        // containing the corresponding Locales to be added
        $locales = array();

//        var_dump($value);

        // Preprocess the value to remove all whitespace
        $value = str_replace(" ", "", $value);

        // Process each comma-delimited language specification
        $values = explode(",", $value);
        foreach($values as $entry) {

            // Extract the quality factor for this entry
            $quality = 1.0;
            $semi = strpos($entry, ";q=");
            if ($semi !== false) {
                try {
                    $quality = doubleval(substr($entry, $semi + 3));
                } catch (Exception $e) {
                    $quality = 0.0;
                }
                $entry = substr($entry, 0, $semi);
            }

            // Skip entries we are not going to keep track of
            if ($quality < 0.00005)
                continue;       // Zero ($or effectively zero) quality factors
            if ("*" == $entry)
                continue;       // FIXME - "*" entries are not handled

            // Extract the language and country for this entry
            $language = null;
            $country = null;
            $variant = null;
            $dash = strpos($entry, '-');
            if ($dash === false) {
                $language = $entry;
                $country = "";
                $variant = "";
            } else {
                $language = substr($entry, 0, $dash);
                $country = substr($entry, $dash + 1);
                $vDash = strpos($country, '-');
                if ($vDash !== false) {
                    $cTemp = substr($country, 0, $vDash);
                    $variant = substr($country, $vDash + 1);
                    $country = $cTemp;
                } else {
                    $variant = "";
                }
            }

            if (!$this->isAlpha($language) || !$this->isAlpha($country) || !$this->isAlpha($variant)) {
                continue;
            }

            // Add a new Locale to the list of Locales for this quality level
            $locale = new Locale($language, $country, $variant);
            $key = "q_" . strval($quality * -1);  // Reverse the order

            $values = array_key_exists($key, $locales) ?
                $locales[$key] : null;

            if ($values === null) {
                $locales[$key] = array();
            }
            $locales[$key][] = $locale;


        }

        // Process the quality values in highest->lowest order ($due to
        // negating the Double value when creating the key)
        $keys = array_keys($locales);

        foreach ($keys as $key) {
            $list = $locales[$key];
            foreach ($list as $locale) {
                $this->addLocale($locale);
            }
        }

    }


    private function isAlpha($value) {
        for ($i = 0; $i < strlen($value); $i++) {
            $c = $value[$i];
            if (preg_match("/[a-zA-Z]/", $c) == 0) {
                return false;
            }
        }
        return true;
    }


    public function __toString() {

        $sb = "Request[";

        $sb .= "\n\tattributes=(";
        $names = $this->getAttributeNames();
        for($i=0; $i < count($names); $i++) {
            try {
                $sb .= "\n\t\t".$names[$i]."=".is_object($this->getAttribute($names[$i])) ?
                    get_class($this->getAttribute($names[$i])) : strval($this->getAttribute($names[$i]));
                if($i < count($names) - 1)
                    $sb .= ", ";
            } catch(Exception $e) {
                ;
            }
        }
        $sb .= ")";
        $sb .= ",\n\tcontextPath=".$this->getContextPath();
        $sb .= ",\n\tisRequestedSessionIdFromCookie=".($this->isRequestedSessionIdFromCookie() ? "Yes" : "No");
        $sb .= ",\n\tisRequestedSessionIdFromUrl=".($this->isRequestedSessionIdFromUrl() ? "Yes" : "No");
        $sb .= ",\n\tlocale=".$this->getLocale();
        $sb .= ",\n\tmethod=".$this->getMethod();
        $sb .= "\n\tnotes=(";
        $notes = $this->getNoteNames();
        for($i=0; $i < count($notes); $i++) {
            try {
                $sb .= "\n\t\t".$notes[$i]."=".is_object($this->getNote($notes[$i])) ?
                    get_class($this->getNote($notes[$i])) : strval($this->getNote($notes[$i]));
                if($i < count($notes) - 1)
                    $sb .= ",";
            } catch(Exception $e) {
                ;
            }
        }
        $sb .= ")";
        $sb .= "\n\tparameters=(";
        $parameters = $this->getParameterNames();
        for($i=0; $i < count($parameters); $i++) {
            try {
                $sb .= "\n\t\t".$parameters[$i]."=".strval($this->getParameter($parameters[$i]));
                if($i < count($parameters) - 1)
                    $sb .= ",";
            } catch(Exception $e) {
                ;
            }
        }
        $sb .= "\n\t)";
        $sb .= ",\n\tpathInfo=".$this->getPathInfo();
        $sb .= ",\n\tprotocol=".$this->getProtocol();
        $sb .= ",\n\tqueryString=".$this->getQueryString();
        $sb .= ",\n\trequestURI=".$this->getRequestURI();
        $sb .= ",\n\tscheme=".$this->getScheme();
        $sb .= ",\n\tserverName=".$this->getServerName();
        $sb .= ",\n\tserverPort=".$this->getServerPort();
        $sb .= ",\n\tservletPath=".$this->getServletPath();
        $sb .= ",\n\tuserPrincipal=".$this->getUserPrincipal();

        $sb .= "\n]";

        return $sb;

    }

}

?>